package com.beta.cat.utils;

import lombok.extern.slf4j.Slf4j;
import org.apache.commons.beanutils.BeanUtils;
import org.apache.commons.beanutils.BeanUtilsBean;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.exception.ExceptionUtils;
import org.apache.commons.lang3.reflect.MethodUtils;
import org.apache.commons.lang3.time.DateUtils;
import org.springframework.beans.BeanWrapper;
import org.springframework.beans.BeanWrapperImpl;

import java.beans.BeanInfo;
import java.beans.Introspector;
import java.beans.PropertyDescriptor;
import java.lang.reflect.Method;
import java.util.*;

@Slf4j
public class ReflectUtils {
    static {
        //将BeanUtils中数字转换时，null时不要默认为0
        BeanUtilsBean.getInstance().getConvertUtils().register(false, true, 0);
    }

    /**
     * 将orig中非null的属性值复制到dest对应的属性中
     *
     * @param orig
     * @param dest
     */
    public static void copyNotNullProperties(Object orig, Object dest) {
        if (orig == null || dest == null) {
            return;
        }
        for (Class<?> cl = orig.getClass(); cl != Object.class; cl = cl.getSuperclass()) {
            Method[] ms = cl.getDeclaredMethods();
            if (ms != null && ms.length > 0) {
                for (Method m : ms) {
                    String name = m.getName();
                    if (!name.startsWith("get")) {// 获取get方法
                        continue;
                    }
                    try {
                        Object value = m.invoke(orig);
                        if (value == null) {
                            continue;
                        }
                        String propertyName = name.substring(3);
                        propertyName = propertyName.substring(0, 1).toLowerCase() + propertyName.substring(1);

                        BeanUtils.copyProperty(dest, propertyName, value);
                    } catch (Exception e) {
                        if (!ExceptionUtils.getRootCauseMessage(e).contains(" has no setter method in class ")) {
                            log.warn(name + " 方法属性复制失败：" + e.getMessage(), e);
                        }
                    }
                }
            }
        }
    }

    /**
     * 获取对象的非空属性名称
     */
    public static Set<String> getNotNullProperties(Object src) {
        if (src == null) {
            return null;
        }
        // 1.获取Bean
        BeanWrapper srcBean = new BeanWrapperImpl(src);
        // 2.获取Bean的属性描述
        PropertyDescriptor[] pds = srcBean.getPropertyDescriptors();
        // 3.获取Bean的空属性
        Set<String> properties = new HashSet<>();
        for (PropertyDescriptor propertyDescriptor : pds) {
            String propertyName = propertyDescriptor.getName();
            Object propertyValue = srcBean.getPropertyValue(propertyName);
            if (propertyValue != null) {
                properties.add(propertyName);
            }
        }
        return properties;
    }

    /**
     * 往对象中设置值，不报错
     *
     * @param obj   对象实体
     * @param name  属性名称
     * @param value 具体值
     * @return true-成功；false-失败
     */
    public static boolean setProperty(Object obj, String name, Object value) {
        try {
            BeanUtils.setProperty(obj, name, value);
            return true;
        } catch (Exception e) {
            return false;
        }
    }

    /**
     * 使用getter读取src对象中的数据，然后使用setter赋值到target对象中。
     *
     * @param src      源对象
     * @param target   目标对象
     * @param property 属性名称
     * @return true-成功；false-失败
     */
    public static boolean copyProperty(Object src, Object target, String property) {
        try {
            if (src == null || target == null || StringUtils.isBlank(property)) {
                return false;
            }
            String getter = "get" + property.substring(0, 1).toUpperCase() + property.substring(1);
            Object value = src.getClass().getMethod(getter).invoke(src);
            BeanUtils.setProperty(target, property, value);
            return true;
        } catch (Exception e) {
            return false;
        }
    }

    /**
     * 读取指定对象中指定属性的值，如果读取失败，则返回null
     *
     * @param entity    实体对象
     * @param fieldName 属性名称
     * @return 返回读取到的值
     */
    public static Object readField(Object entity, String fieldName) {
        if (entity == null || StringUtils.isBlank(fieldName)) {
            return null;
        }
        try {
            String getter = "get" + fieldName.substring(0, 1).toUpperCase() + fieldName.substring(1);
            Object value = entity.getClass().getMethod(getter).invoke(entity);
            return value;
        } catch (Exception e) {
            if (!(e instanceof NoSuchMethodException)) {
                log.debug("readField failed：obj={};fieldName={}", entity, fieldName, e);
            }
            return null;
        }
    }

    /**
     * 把对象中的属性和值转换为map形式存放，key就是属性名，value就是属性值
     *
     * @param entity 实体对象
     * @return 转换成对应的Map结构
     */
    public static Map<String, Object> entityToMap(Object entity) {
        Map<String, Object> returnMap = new HashMap<>();
        if (entity == null) {
            return returnMap;
        }
        Class<?> type = entity.getClass();
        try {
            BeanInfo beanInfo = Introspector.getBeanInfo(type);
            PropertyDescriptor[] propertyDescriptors = beanInfo.getPropertyDescriptors();
            for (PropertyDescriptor descriptor : propertyDescriptors) {
                String propertyName = descriptor.getName();
                if (!propertyName.equals("class")) {
                    Method readMethod = descriptor.getReadMethod();
                    Object result = readMethod.invoke(entity);
                    if (result != null) {
                        returnMap.put(propertyName, result);
                    } else {
                        returnMap.put(propertyName, "");
                    }
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
        return returnMap;
    }

    /**
     * 将指定的日志格式化成 yyyy-MM-dd 00:00:00
     *
     * @param time 具体日期
     * @return 返回指定日期0点时间
     */
    public static Date processStartTime(Date time) {
        if (time == null) {
            return null;
        }
        return DateUtils.truncate(time, Calendar.DATE);
    }

    /**
     * 将指定的日志格式化成 yyyy-MM-dd 23:59:59
     *
     * @param time 具体日期
     * @return 返回指定日期24点时间
     */
    public static Date processEndTime(Date time) {
        if (time == null) {
            return null;
        }
        time = DateUtils.addDays(time, 1);
        time = DateUtils.truncate(time, Calendar.DATE);
        return DateUtils.addMilliseconds(time, -1);
    }

    /**
     * 将map中的表字段及值映射到对象中，即：将下划线分隔的名称，转换到驼峰格式的对象中
     *
     * @param row         一条数据
     * @param entityClass 实体对象类
     * @return 返回转换成的实体对象
     */
    public static <T> T mapToEntity(Map<String, Object> row, Class<T> entityClass) {
        try {
            T t = entityClass.newInstance();
            for (String field : row.keySet()) {
                Object value = row.get(field);
                if (value == null) {
                    continue;
                }
                StringBuilder name = new StringBuilder();
                boolean splitor = false;
                for (char c : field.toCharArray()) {
                    if (c == '_') {
                        splitor = true;
                        continue;
                    } else {
                        if (splitor) {
                            name.append((c + "").toUpperCase());
                            splitor = false;
                        } else {
                            name.append(c);
                        }
                    }
                }
                try {
                    BeanUtils.setProperty(t, name.toString(), value);
                } catch (Exception ignore) {
                }
            }
            return t;
        } catch (Exception e) {
            log.warn("mapToEntity error!row={};entityClass={}", row, entityClass, e);
        }
        return null;
    }

    /**
     * 将对象中指定的属性设置为null
     *
     * @param entity 对象实例
     * @param fields 需要设置为null的所有属性名
     */
    public static void setPropertyToNull(Object entity, String... fields) {
        if (fields == null || fields.length < 1) {
            return;
        }
        for (String f : fields) {
            setProperty(entity, f, null);
        }
    }

    /**
     * 将一个实体类转换成指定类型的类
     *
     * @param entity      实体对象
     * @param targetClass 目标类，这个类要有无参的构造函数
     * @param fieldMap    指定不同名称的属性之间复制值，其格式为：origField:targetFiled，如：ruldId:metaRuleId
     */
    public static <T> T convertEntity(Object entity, Class<T> targetClass, String... fieldMap) {
        if (targetClass == null) {
            throw new IllegalArgumentException("请指定 targetClass！");
        }
        if (entity == null) {
            return null;
        }
        try {
            T target = null;
            Method method = null;
            try {
                method = MethodUtils.getMatchingAccessibleMethod(targetClass, "builder");
            } catch (Exception ex) {
            }
            if (method != null) {
                Object builder = MethodUtils.invokeStaticMethod(targetClass, "builder");
                target = (T) MethodUtils.invokeMethod(builder, "build");
            } else {
                target = targetClass.newInstance();
            }
            copyNotNullProperties(entity, target);
            if (fieldMap != null && fieldMap.length > 0) {//如果指定了特殊对应属性名，则进行处理
                for (String field : fieldMap) {
                    //将填写的格式归一化处理
                    field = StringUtils.trimToEmpty(field).replace(" ", "").replaceAll(":+", ":");
                    if (field.isEmpty() || !field.contains(":")) {
                        continue;
                    }
                    String origField = field.split(":")[0];
                    String targetFiled = field.split(":")[1];
                    Object val = readField(entity, origField);
                    if (val == null) {
                        continue;
                    }
                    setProperty(target, targetFiled, val);
                }
            }
            return target;
        } catch (Exception e) {
            throw new IllegalArgumentException(entity.getClass() + "转换到" + targetClass + "失败！", e);
        }
    }

    /**
     * 将一个实体队列，转换为指定的类的实体队列
     *
     * @param entities    实体对象队列
     * @param targetClass 目标类，这个类要有无参的构造函数
     * @param fieldMap    指定不同名称的属性之间复制值，其格式为：origField:targetFiled，如：ruldId:metaRuleId
     */
    public static <T> List<T> convertEntities(List<?> entities, Class<T> targetClass, String... fieldMap) {
        if (targetClass == null) {
            throw new IllegalArgumentException("请指定 targetClass！");
        }
        if (entities == null) {
            return null;
        }
        List<T> ret = new ArrayList<>();
        if (entities.isEmpty()) {
            return ret;
        }
        for (Object entity : entities) {
            ret.add(convertEntity(entity, targetClass, fieldMap));
        }
        return ret;
    }
}
